#include <assert.h>
#include <errno.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <err.h>
#include <sysexits.h>

#include "bezier.h"

#define ERR(e, r)		\
	do {			\
		errno = (e);	\
		return (r);	\
	} while (0)

static int		 readspline(FILE *, double **, size_t *);
static int		 splineappend(double *, double **, size_t *);
static int		 addpoint(double **, size_t *, double, double);
static void		 renderline(double *, size_t, uint32_t,
			    uint32_t *, uint32_t);
static void		 printhdr(FILE *, uint32_t, uint32_t);
static void		 printline(FILE *, uint32_t *, uint32_t);
static double		 scand(FILE *, int *);
static int		 strtou32(const char *, uint32_t *);

int
main(int argc, char *argv[])
{
	double *bz = NULL;
	size_t count = 0;
	size_t i;
	uint32_t *line;
	uint32_t w = 512;
	uint32_t h = 512;
	uint32_t y;
	int c;

	while ((c = getopt(argc, argv, "h:w:")) != -1)
		switch (c) {
		case 'h':
			if (strtou32(optarg, &h) != 0)
				err(EX_USAGE, "Bad number %s", optarg);
			break;
		case 'w':
			if (strtou32(optarg, &w) != 0)
				err(EX_USAGE, "Bad number %s", optarg);
			break;
		default:
			goto usage;
		}
	argc -= optind;
	argv += optind;
	if ((line = malloc(w * sizeof(uint32_t))) == NULL)
		err(EX_OSERR, "Could not allocate buffer");
	for (;;) {
		bz = NULL;
		count = 0;
		if (readspline(stdin, &bz, &count) < 0)
			break;
		(void)fprintf(stderr, "spline:");
		for (i = 0; i + 6 <= count; i += 6)
			(void)fprintf(stderr, "\t%g,%g %g,%g %g,%g\n",
			    bz[i + 0], bz[i + 1], bz[i + 2], bz[i + 3],
			    bz[i + 4], bz[i + 5]);
		for (y = 0; y <= h; y++) {
			if (y == 0)
				printhdr(stdout, w, h);
			renderline(bz, count, y, line, w);
			printline(stdout, line, w);
		}
		free(bz);
	}
	free(line);
	return (0);
usage:
	(void)fprintf(stderr,
	    "usage: ffspline [-h height] [-w width]\n");
	return (EX_USAGE);
}

int
readspline(FILE *fp, double **d, size_t *s)
{
	double start[4];
	double tmp[6];
	int eof = 0;

	tmp[0] = scand(fp, &eof);
	tmp[1] = scand(fp, &eof);
	tmp[2] = scand(fp, &eof);
	tmp[3] = scand(fp, &eof);
	if (eof)
		return (-1);
	(void)memcpy(start, tmp, sizeof(start));
	for (;; (void)memmove(tmp, tmp + 2, sizeof(double) * 4)) {
		tmp[4] = scand(fp, &eof);
		tmp[5] = scand(fp, &eof);
		if (eof)
			break;
		if (splineappend(tmp, d, s) != 0)
			goto err;
	}
	(void)memcpy(tmp + 4, start, sizeof(double) * 2);
	if (splineappend(tmp, d, s) != 0)
		goto err;
	(void)memmove(tmp, tmp + 2, sizeof(double) * 4);
	(void)memcpy(tmp + 4, start + 2, sizeof(double) * 2);
	if (splineappend(tmp, d, s) != 0)
		goto err;
	return (0);
err:
	return (-1);
}

int
splineappend(double *d, double **da, size_t *s)
{

	assert(d != NULL && s != NULL);
	if (d[0] == d[4] && d[1] == d[5])
		return (0);
	if (addpoint(da, s, (d[0] + d[2]) / 2, (d[1] + d[3]) / 2) < 0 ||
	    addpoint(da, s, d[2], d[3]) < 0 ||
	    addpoint(da, s, (d[2] + d[4]) / 2, (d[3] + d[5]) / 2) < 0)
		return (-1);
	return (0);
}

int
addpoint(double **e, size_t *s, double x, double y)
{
	void *p;

	assert(e != NULL && s != NULL);
	if ((p = reallocarray(*e, *s + 1, sizeof(double) * 2)) == NULL)
		return (-1);
	*e = p;
	(*e)[*s + 0] = x;
	(*e)[*s + 1] = y;
	(*s) += 2;
	return (0);
}

void
renderline(double *d, size_t s, uint32_t y, uint32_t *line, uint32_t w)
{
	double *ad;
	double *bd;
	double *ld;
	ssize_t as;
	ssize_t bs;
	ssize_t ls;
	size_t i;
	double low;
	double high;
	double a;
	uint32_t x;

	assert(d != NULL && line != NULL);
	(void)memset(line, 0, w * sizeof(uint32_t));
	/* Get the curve for the interval [y, y+1] */
	ad = NULL;
	as = bz2split(d, s, &ad, NULL, 0, y, BZ_HIGH_Y);
	if (as <= 0)
		return;
	ld = NULL;
	ls = bz2split(ad, as, &ld, NULL, 0, y + 1, BZ_LOW_Y);
	free(ad);
	if (ls <= 0)
		return;
	for (low = w, high = 0, i = 0; i + 2 <= ls; i += 2) {
		if (ld[i] < low)
			low = ld[i];
		if (ld[i] > high)
			high = ld[i];
	}
	if (low < 0)
		low = 0;
	if (++high > (double)w)
		high = w;
	for (x = floor(low); x < (uint32_t)high; x++) {
		/* Get the curve for the interval [x, x+1] */
		ad = NULL;
		bd = NULL;
		as = bz2split(ld, ls, &ad, NULL, 0, x, BZ_HIGH_X);
		if (as < 0)
			goto err;
		bs = bz2split(ad, as, &bd, NULL, 0, x + 1, BZ_LOW_X);
		if (bs < 0)
			goto err;
		if (bs == 0)
			a = 0;
		else if (bz2aa(bd, bs, &a) <= 0)
			goto err;
		a = fabs(a);
		if (a > 1)
			a = 1;
		line[x] = a * (double)UINT32_MAX;
		free(ad);
		free(bd);
	}
	free(ld);
	return;
err:
	free(ld);
	if (ad != NULL)
		free(ad);
	if (bd != NULL)
		free(bd);
	return;
}

void
printhdr(FILE *fp, uint32_t w, uint32_t h)
{
	uint8_t out[16];

	(void)memcpy(out, "farbfeld", 8);
	out[8] = (w >> 24) & 0xFF;
	out[9] = (w >> 16) & 0xFF;
	out[10] = (w >> 8) & 0xFF;
	out[11] = (w >> 0) & 0xFF;
	out[12] = (h >> 24) & 0xFF;
	out[13] = (h >> 16) & 0xFF;
	out[14] = (h >> 8) & 0xFF;
	out[15] = (h >> 0) & 0xFF;
	(void)fwrite(out, sizeof(out), 1, fp);
}

void
printline(FILE *fp, uint32_t *ln, uint32_t w)
{
	uint8_t out[8];
	uint32_t v;
	uint32_t i;

	for (i = 0; i < w; i++) {
		v = ln[i] >> 16;
		out[0] = (v >> 8) & 0xFF;
		out[1] = (v >> 0) & 0xFF;
		(void)memcpy(out + 2, out, 2);
		(void)memcpy(out + 4, out, 2);
		out[6] = 0xFF;
		out[7] = 0xFF;
		(void)fwrite(out, sizeof(out), 1, fp);
	}
}

double
scand(FILE *fp, int *eof)
{
	double d;
	double f;
	int state = 0;
	int n = 0;
	int c;

	assert(fp != NULL && eof != NULL);
	/* [^0-9]*[-+]?[0-9]+(.[0-9]*)? */
	while ((c = fgetc(fp)) != EOF)
		switch (state) {
		case 0:
			if (c >= '0' && c <= '9') {
				ungetc(c, fp);
				d = 0;
				state = 1;
			} else if (c == '-' || c == '+') {
				d = 0;
				n = c == '-';
				state = 1;
			}
			break;
		case 1:
			if (c >= '0' && c <= '9') {
				ungetc(c, fp);
				state = 2;
			} else {
				ungetc(c, fp);
				state = 0;
			}
			break;
		case 2:
			if (c >= '0' && c <= '9') {
				d = d * 10 + (c - '0');
			} else if (c == '.') {
				f = 0.1;
				state = 3;
			} else {
				ungetc(c, fp);
				return (n ? -d : d);
			}
			break;
		case 3:
			if (c >= '0' && c <= '9') {
				d += f * (c - '0');
				f /= 10;
			} else {
				ungetc(c, fp);
				return (n ? -d : d);
			}
			break;
		}
	*eof = 1;
	return (0);
}

int
strtou32(const char *s, uint32_t *o)
{
	char *p;
	unsigned long ul;

	if (s == NULL || o == NULL)
		ERR(EFAULT, -1);
	errno = 0;
	if ((ul = strtoul(s, &p, 0)) > UINT32_MAX)
		errno = ERANGE;
	if (errno == 0 && (p == s || *p != '\0'))
		errno = EINVAL;
	*o = ul;
	return (errno == 0 ? 0 : -1);
}
